package com.tiny.mirror.service.product.common.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.tiny.mirror.basic.db.mybatis.EasySqlInjector;
import com.tiny.mirror.basic.db.mybatis.handler.BaseMetaObjectHandler;
import com.tiny.mirror.basic.db.util.DbConfigUtil;
import com.tiny.mirror.service.product.common.constants.DBConstants;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.type.EnumTypeHandler;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.config.algorithm.AlgorithmConfiguration;
import org.apache.shardingsphere.readwritesplitting.api.ReadwriteSplittingRuleConfiguration;
import org.apache.shardingsphere.readwritesplitting.api.rule.ReadwriteSplittingDataSourceRuleConfiguration;
import org.apache.shardingsphere.readwritesplitting.api.strategy.StaticReadwriteSplittingStrategyConfiguration;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Slf4j
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@EnableAspectJAutoProxy
public class DBConfig {

  private static final String DEMO_MASTER = "demo_master";
  private static final String DEMO_SLAVE = "demo_slave";
  private static final String MASTER_SLAVE = "master_slave";

  @Bean(name = DBConstants.DEMO_MAPPER_SCANNER)
  public static MapperScannerConfigurer mapperScannerConfigurer() {
    MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
    mapperScannerConfigurer.setBasePackage(
        "com.tiny.mirror.service.product.infrastructure.repository.db"
    );
    mapperScannerConfigurer.setSqlSessionFactoryBeanName(DBConstants.DEMO_SQL_SESSION_FACTORY);
    return mapperScannerConfigurer;
  }

  @Bean
  @ConfigurationProperties(prefix = "spring.datasource")
  public DataSource datasourceMaster() {
    return DataSourceBuilder.create().type(HikariDataSource.class).build();
  }

  @Bean
  @ConfigurationProperties(prefix = "spring.datasource.slave")
  public DataSource datasourceSlave() {
    return DataSourceBuilder.create().type(HikariDataSource.class).build();
  }

  @Primary
  @Bean(name = DBConstants.DEMO_DATASOURCE)
  public DataSource dataSource() throws SQLException {
    Map<String, DataSource> dataSourceMap = Maps.newLinkedHashMap(
        new ImmutableMap.Builder().put(DEMO_MASTER, datasourceMaster()).build()
    );
    DataSource slaveDataSource = datasourceSlave();
    try (Connection connection = slaveDataSource.getConnection()) {
      if (connection.isValid(5)) {
        dataSourceMap.put(DEMO_SLAVE, slaveDataSource);
      }
    } catch (SQLException e) {
      log.debug("connection error!", e);
    }
    //主从数据源配置
    List<ReadwriteSplittingDataSourceRuleConfiguration> dsConfig = Lists.newArrayList();
    dsConfig.add(
        new ReadwriteSplittingDataSourceRuleConfiguration(
            MASTER_SLAVE,
            new StaticReadwriteSplittingStrategyConfiguration(
                DEMO_MASTER, Lists.newArrayList(DEMO_SLAVE)
            ), null, "load_balancer"
        )
    );
    //负载均衡算法配置
    Map<String, AlgorithmConfiguration> loadBalanceMap = Maps.newHashMap();
    loadBalanceMap.put(
        "load_balancer", new AlgorithmConfiguration("ROUND_ROBIN", new Properties())
    );
    ReadwriteSplittingRuleConfiguration ruleConfig = new ReadwriteSplittingRuleConfiguration(
        dsConfig, loadBalanceMap
    );
    Properties properties = new Properties();
    properties.put("sql.show", true);
    return ShardingSphereDataSourceFactory.createDataSource(
        dataSourceMap, Lists.newArrayList(ruleConfig), properties
    );
  }

  @Bean(name = DBConstants.DEMO_TRANSACTION_MANAGER)
  public PlatformTransactionManager transactionManager() throws SQLException {
    return new DataSourceTransactionManager(dataSource());
  }

  @Bean(name = DBConstants.DEMO_SQL_SESSION_FACTORY)
  public MybatisSqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
    MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
    mybatisSqlSessionFactoryBean.setDataSource(dataSource());
    mybatisSqlSessionFactoryBean.setDefaultEnumTypeHandler(EnumTypeHandler.class);
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    mybatisSqlSessionFactoryBean.setMapperLocations(
        resolver.getResources("classpath:mybatis/*.xml")
    );
    mybatisSqlSessionFactoryBean.setPlugins(
        new Interceptor[]{DbConfigUtil.sqlExecutionWatcher()}
    );
    GlobalConfig globalConfig = new GlobalConfig();
    globalConfig.setBanner(false);
    globalConfig.setMetaObjectHandler(new BaseMetaObjectHandler());
    globalConfig.setSqlInjector(new EasySqlInjector());
    mybatisSqlSessionFactoryBean.setGlobalConfig(globalConfig);
    mybatisSqlSessionFactoryBean.setPlugins(mybatisPlusInterceptor());
    return mybatisSqlSessionFactoryBean;
  }

  @Bean
  public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
  }
}
